﻿using ICSharpCode.SharpZipLib.Zip;
using System;
using System.IO;
using System.Text;

namespace DotNetCommon
{
    /// <summary>
    /// 压缩和解压缩帮助类, 引用SharpZipLib
    /// </summary>
    public class ZipHelper
    {
        static ZipHelper()
        {
            //消除文件名中文乱码
            //ICSharpCode.SharpZipLib.Zip.ZipStrings.CodePage = Encoding.UTF8.CodePage;
        }

        #region 压缩文件

        /// <summary>
        /// 从流中压缩文件到流
        /// </summary>
        /// <param name="outputStream"></param>
        /// <param name="files"></param>
        public static void ZipFile(Stream outputStream, params (Stream srcFileStream, string fileName)[] files)
        {
            var zipOutputStream = new ZipOutputStream(outputStream);
            foreach (var i in files)
            {
                ZipSingleStream(zipOutputStream, i.srcFileStream, i.fileName);
            };
            zipOutputStream.Finish();
            zipOutputStream.Close();
        }

        /// <summary>
        /// 压缩文件
        /// </summary>
        /// <param name="dest">目的文件,如: c:\test.zip</param>
        /// <param name="fileAbsPaths">文件来源列表,如: c:\test.txt,d:\test.db</param>
        public static void ZipFile(string dest, params string[] fileAbsPaths)
        {
            using (var fs = new FileStream(dest, FileMode.Create))
            {
                var zipOutputStream = new ZipOutputStream(fs);
                foreach (var i in fileAbsPaths)
                {
                    ZipSingleFile(zipOutputStream, i);
                };
                zipOutputStream.Finish();
                zipOutputStream.Close();
            }
        }

        /// <summary>
        /// 压缩文件
        /// </summary>
        /// <param name="dest">目的文件,如: c:\test.zip</param>
        /// <param name="files">文件来源列表,如: arr[("test.txt","c:\test.txt"),("db\test.db","c:\test.db")]</param>
        public static void ZipFile(string dest, params (string fileAbsPath, string fileName)[] files)
        {
            using (var fs = new FileStream(dest, FileMode.Create))
            {
                var zipOutputStream = new ZipOutputStream(fs);
                foreach (var i in files)
                {
                    ZipSingleFile(zipOutputStream, i.fileAbsPath, i.fileName);
                };
                zipOutputStream.Finish();
                zipOutputStream.Close();
            }
        }

        /// <summary>
        /// 压缩单个文件到流
        /// </summary>
        /// <param name="zipOutputStream">目标流</param>
        /// <param name="filePath">文件绝对路径</param>
        /// <param name="relativeName">压缩包内的路径</param>
        private static void ZipSingleFile(ZipOutputStream zipOutputStream, string filePath, string relativeName = "")
        {
            var fileInfo = new FileInfo(filePath);
            if (string.IsNullOrWhiteSpace(relativeName)) relativeName = Path.GetFileName(filePath);
            ZipEntry zipEntry = new ZipEntry(Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(relativeName)));
            zipEntry.DateTime = DateTime.Now;
            zipEntry.Size = fileInfo.Length;
            zipOutputStream.PutNextEntry(zipEntry);
            using (var fs = new FileStream(fileInfo.FullName, FileMode.Open))
            {
                fs.CopyTo(zipOutputStream);
            }
        }

        /// <summary>
        /// 压缩单个文件到流
        /// </summary>
        /// <param name="zipOutputStream">目标流</param>
        /// <param name="stream">流</param>
        /// <param name="relativeName">压缩包内的路径</param>
        private static void ZipSingleStream(ZipOutputStream zipOutputStream, Stream stream, string relativeName = "")
        {
            if (string.IsNullOrWhiteSpace(relativeName)) relativeName = Guid.NewGuid().ToString();
            ZipEntry zipEntry = new ZipEntry(Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(relativeName)));
            zipEntry.DateTime = DateTime.Now;
            var len = stream.Length - stream.Position;
            zipEntry.Size = len;
            zipOutputStream.PutNextEntry(zipEntry);
            stream.CopyTo(zipOutputStream);
        }

        private static void ZipSingleFolder(ZipOutputStream zipOutputStream, string folderPath, string folderName = "")
        {
            string[] directorys = Directory.GetDirectories(folderPath);
            foreach (var i in directorys)
            {
                var tmp = Path.Combine(folderName, new DirectoryInfo(i).Name);
                ZipSingleFolder(zipOutputStream, i, tmp);
            }
            string[] files = Directory.GetFiles(folderPath);
            foreach (var i in files)
            {
                ZipSingleFile(zipOutputStream, i, Path.Combine(folderName, Path.GetFileName(i)));
            }
        }

        /// <summary>
        /// 递归压缩文件夹
        /// </summary>
        /// <param name="dest">目的文件名称,如: c:\test.zip</param>
        /// <param name="folderPaths">压缩的目录列表,如: c:\test,d:\jack</param>
        public static void ZipFolder(string dest, params string[] folderPaths)
        {
            using (var fs = new FileStream(dest, FileMode.Create))
            {
                var zipOutputStream = new ZipOutputStream(fs);
                foreach (var i in folderPaths)
                {
                    ZipSingleFolder(zipOutputStream, i, new DirectoryInfo(i).Name);
                };
                zipOutputStream.Finish();
                zipOutputStream.Close();
            }
        }

        /// <summary>
        /// 递归压缩文件夹里的内容,不包含文件夹本身(区别:ZipFolder() )
        /// </summary>
        /// <param name="dest">目的文件名称,如: c:\test.zip</param>
        /// <param name="folderPath">压缩的目录列表,如: c:\test</param>
        public static void ZipFolderContent(string dest, string folderPath)
        {
            using (var fs = new FileStream(dest, FileMode.Create))
            {
                var zipOutputStream = new ZipOutputStream(fs);
                string[] directorys = Directory.GetDirectories(folderPath);
                foreach (var i in directorys)
                {
                    var tmp = Path.Combine(new DirectoryInfo(i).Name);
                    ZipSingleFolder(zipOutputStream, i, tmp);
                }
                string[] files = Directory.GetFiles(folderPath);
                foreach (var i in files)
                {
                    ZipSingleFile(zipOutputStream, i, Path.GetFileName(i));
                }
                zipOutputStream.Finish();
                zipOutputStream.Close();
            }
        }
        #endregion

        #region 解压缩文件
        /// <summary>
        /// 解压缩文件到指定文件夹
        /// </summary>
        /// <param name="srcZipFile">源压缩文件</param>
        /// <param name="destDir">目标文件夹</param>
        /// <returns></returns>
        public static void UnZipFile(string srcZipFile, string destDir)
        {
            if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
            using (var stream = new ZipInputStream(File.OpenRead(srcZipFile.Trim())))
            {
                ZipEntry entry;
                while ((entry = stream.GetNextEntry()) != null)
                {
                    if (entry.IsDirectory)
                    {
                        //目录,创建新目录即可
                        var path = Path.Combine(destDir, entry.Name);
                        if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                        continue;
                    }
                    else if (entry.IsFile)
                    {
                        //文件,将文件解压缩
                        var path = Path.Combine(destDir, entry.Name);
                        var dir = Path.GetDirectoryName(path);
                        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                        using (var fs = File.Create(path))
                        {
                            int size = 2048;
                            byte[] data = new byte[2048];
                            while (true)
                            {
                                size = stream.Read(data, 0, data.Length);
                                if (size > 0)
                                {
                                    fs.Write(data, 0, size);
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        #endregion
    }
}
